﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using alib;
using alib.BitArray;
using alib.Character;
using alib.Collections;
using alib.Collections.ReadOnly;
using alib.Debugging;
using alib.Dictionary;
using alib.Enumerable;
using alib.Reflection;
using alib.String;

namespace agree.transfer.old
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class TransferMap : identity_named, ITransferGrammarItem
	{
		public TransferMap(TransferGrammar tg, String name, String body)
			: base(tg, name)
		{
			this.tg = tg;
			ParseMap(body);
		}

		public TransferGrammar tg;
		public TransferGrammar TransferGrammar { get { return tg; } }

		public Edge.Flag[][] flags;

		protected void ParseMap(String body)
		{
			if (body.Count(ch => ch == '(') != body.Count(ch => ch == ')'))
				throw new TransferGrammarLoadException("unbalanced parentheses in transfer map '{0}'", Name);

			int ixp = body.IndexOf('(');
			if (ixp == -1)
				throw new TransferGrammarLoadException("expected '(' in transfer map '{0}'", Name);
			String pkw = body.Substring(0, ixp);
			body = body.Substring(ixp);

			String[] lang_names = pkw.Split(Charset.sp, StringSplitOptions.RemoveEmptyEntries);
			int _arity = lang_names.Length;
			GrammarRef[] rg_gr = new GrammarRef[lang_names.Length];

			for (int i = 0; i < _arity; i++)
				if ((rg_gr[i] = tg[lang_names[i]]) == null)
					throw new TransferGrammarLoadException("invalid language key '{0}' in transfer map '{1}'", lang_names[i], Name);

			String[] rgs = body.Split(Charset.parens_space, StringSplitOptions.RemoveEmptyEntries);
			int c_entries = rgs.Length / _arity;
			if (rgs.Length != c_entries * _arity)
				throw new TransferGrammarLoadException("incorrect number of tuples in transfer map '{0}'", Name);

			this.flags = new Edge.Flag[c_entries][];

			for (int _grp = 0, _base = 0; _grp < c_entries; _grp++)
			{
				Edge.Flag[] cur_set = flags[_grp] = new Edge.Flag[_arity];
				for (int j = 0; j < _arity; j++)
				{
					GrammarRef gr = rg_gr[j];
					cur_set[gr.tgix] = gr.g.tu.StringToTypeOrStringValue(rgs[_base + j]);
				}
				_base += _arity;
			}
		}
	};

	public class PriorityMap : TransferMap
	{
		public PriorityMap(TransferGrammar tg, String name, String body)
			: base(tg, name, body)
		{
		}
	};

	public class MultiMap : TransferMap
	{
		public MultiMap(TransferGrammar tg, String name, String body)
			: base(tg, name, body)
		{
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public struct _rule_membership_helper
	{
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		List<TransferRuleGroup> groups;

		public IList<TransferRuleGroup> GroupMembership
		{
			get
			{
				if (groups == null)
					return Collection<TransferRuleGroup>.None;
				return groups;
			}
		}

		public bool IsUsedInGroups { get { return groups != null; } }

		public void AddRuleGroup(TransferRuleGroup trg)
		{
			if (groups == null)
				groups = new List<TransferRuleGroup>();
			groups.Add(trg);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{GetType().Name} {name,nq} Count:{Count}")]
	public abstract class TransferRuleGroup : identity_named, ITransferRule, IList<ITransferRule>
	{
		public static TransferRuleGroup Create(TransferGrammar tg, String name, String body)
		{
			int ixp = body.IndexOf('(');
			if (ixp == -1)
				throw new TransferGrammarLoadException("expected '(' in rule group '{0}'", name);
			String pkw = body.Substring(0, ixp).Trim();
			body = body.Substring(ixp + 1);
			if (String.Compare(pkw, "priority", true) == 0)
				return new Priority(tg, name, body);
			else if (String.Compare(pkw, "branching", true) == 0)
				return new Branching(tg, name, body);
			else
				throw new TransferGrammarLoadException("expected 'priority' or 'exclusive' keyword in rule group '{0}'", name);
		}

		public TransferRuleGroup(TransferGrammar tg, String name, String body)
			: base(tg, name)
		{
			this.tg = tg;
			var arr = body.Split(Charset.sp, StringSplitOptions.RemoveEmptyEntries);
			int c = arr.Length;
			if (--c < 0 || arr[c] != ")")
				throw new TransferGrammarLoadException("expected ')' in rule group '{0}'", name);
			if (c == 0)
				this.subrules = Collection<ITransferRule>.None;
			else
			{
				this.subrules = new ITransferRule[c];
				for (int i = 0; i < c; i++)
				{
					String r_name = arr[i];
					ITransferRule r = tg.rules.Find(trb => String.Compare(r_name, trb.Name, true) == 0);
					if (r == null)
						throw new TransferGrammarLoadException("rule or group '{0}' listed in rule group '{1}' was not defined", r_name, name);
					subrules[i] = r;
					r.AddRuleGroup(this);
				}
			}
		}

		readonly TransferGrammar tg;
		public TransferGrammar TransferGrammar { get { return tg; } }

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		public readonly ITransferRule[] subrules;

		public ITransferRule this[int index]
		{
			get { return subrules[index]; }
			set { throw new InvalidOperationException(); }
		}

		public Tfs Expanded { get { throw not.impl; } }
		public Tfs Definition { get { throw not.impl; } }

		public int IndexOf(ITransferRule item)
		{
			return Array.IndexOf<ITransferRule>(subrules, item);
		}
		public bool Contains(ITransferRule item)
		{
			return Array.IndexOf<ITransferRule>(subrules, item) != -1;
		}
		public void CopyTo(ITransferRule[] array, int arrayIndex)
		{
			Array.Copy(subrules, 0, array, arrayIndex, subrules.Length);
		}
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public int Count { get { return subrules.Length; } }
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public bool IsReadOnly { get { return true; } }
		public IEnumerator<ITransferRule> GetEnumerator() { return subrules.Enumerator(); }
		IEnumerator IEnumerable.GetEnumerator() { return subrules.GetEnumerator(); }
		public void Insert(int index, ITransferRule item) { throw new InvalidOperationException(); }
		public void RemoveAt(int index) { throw new InvalidOperationException(); }
		public void Add(ITransferRule item) { throw new InvalidOperationException(); }
		public void Clear() { throw new InvalidOperationException(); }
		public bool Remove(ITransferRule item) { throw new InvalidOperationException(); }

		public abstract void ApplyRule(MultiRootTfs inp_rels, MultiRootTfs hyp_rels);

		public class Priority : TransferRuleGroup
		{
			public Priority(TransferGrammar tg, String name, String body)
				: base(tg, name, body)
			{
			}

			public override void ApplyRule(MultiRootTfs inp_rels, MultiRootTfs hyp_rels)
			{
				throw not.impl;
			}
		};


		public class Branching : TransferRuleGroup
		{
			public Branching(TransferGrammar tg, String name, String body)
				: base(tg, name, body)
			{
			}

			public override void ApplyRule(MultiRootTfs inp_rels, MultiRootTfs hyp_rels)
			{
				throw not.impl;
			}
		};

		///////////////////////////////////////////////////////////////////////
		/// 
		_rule_membership_helper rgh;
		public bool IsUsedInGroups { get { return rgh.IsUsedInGroups; } }
		public IList<TransferRuleGroup> GroupMembership { get { return rgh.GroupMembership; } }
		public void AddRuleGroup(TransferRuleGroup trg) { rgh.AddRuleGroup(trg); }
		/// 
		///////////////////////////////////////////////////////////////////////

		public long Id
		{
			get { throw new NotImplementedException(); }
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class TransferRule : TransferTfs, ITransferRule, IFeatValueRenderNode
	{
		public static TransferRule Create(IRtParent parent, TransferGrammar tg, String name, String body)
		{
			List<XdlRel> rels = new List<XdlRel>();
			var maps = new SlotDictionary<String, TransferMap>(StringComparer.InvariantCultureIgnoreCase);

			IEnumerator<String> ies = body.Split(Charset.parens).Enumerator();
			while (ies.MoveNext())
			{
				String k = ies.Current.Trim();
				var s_grm = k.Split(new[] { "rels-" }, StringSplitOptions.None);
				if (s_grm.Length == 2)
				{
					if (!ies.MoveNext())
						throw new Exception("missing layer?");

					String v = ies.Current.Trim();
					if (v.Length > 0)
					{
						TransferRelMode trm = new TransferRelMode(tg, s_grm[0], s_grm[1]);
						var xdldefs = XdlRel._get_reldefs(trm, v);
						rels.AddRange(xdldefs);
					}
				}
				else if (k.Length > 0)
				{
					var mm = k.Split(':');
					TransferMap m;
					String tag;
					if (mm.Length != 2 || ((tag = mm[0])[0] != '@' || (m = tg.GetTransferMap(mm[1])) == null))
						throw new TransferGrammarLoadException("invalid map reference '{0}'", k);
					if (maps.ContainsKey(tag))
						throw new TransferGrammarLoadException("tag '{0}' cannot specify multiple maps", tag);
					maps[mm[0]] = m;
				}
			}

			//if (!maps.Keys.SetEquals(xmts.Keys))
			//    throw new TransferGrammarLoadException("Some transfer map names are missing definitions");

			fixup_tags(rels);

#if TRANSFER_GRAMMAR_FLAGS
			List<InOutRelPair>[] InOutRels = makeInOutRels(tg, rels);
			String[] relLabels = rels.Select(r => r.s_tag).ToArray();
#endif

			rels.Sort();

			TfsBuilder.Xdl xdlb = TfsBuilder.Xdl.Build(parent, tg, rels);
			return new TransferRule(parent, tg, name, xdlb, maps);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		static void fixup_tags(IEnumerable<XdlRel> rels)
		{
			var grps = rels.Where(_rel => _rel.s_tag.Length != 0)
				.GroupBy(_rel => _rel.s_tag, StringComparer.OrdinalIgnoreCase)
				.ToArray();

			var grp = grps.FirstOrDefault(_grp => !_grp.MoreThanOne());
			if (grp != null)
				throw new TdlException("Transfer rel tag '{0}' cannot be used only once.", grp.Key);

			Dictionary<String, int> tag_dict = new Dictionary<String, int>(StringComparer.OrdinalIgnoreCase);
			for (int i = 0; i < grps.Length; )
				tag_dict.Add(grps[i].Key, ++i);

			foreach (var rel in rels)
			{
				int i_tag;
				if (rel.s_tag.Length != 0 && tag_dict.TryGetValue(rel.s_tag, out i_tag))
					TransferRelMode.set_tag_num(ref rel.trm, i_tag);
			}
		}

#if TRANSFER_GRAMMAR_FLAGS
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public class InOutRelPair
		{
			public InOutRelPair()
			{
				in_rel = -1;
				out_rel = -1;
			}
			public int in_rel;
			public int out_rel;
		}

		static List<InOutRelPair>[] makeInOutRels(TransferGrammar tg, IList<XdlRel> rels)
		{
			var result = new List<InOutRelPair>[2];
			Dictionary<string, InOutRelPair>[] lbl_dict = new Dictionary<string, InOutRelPair>[2];
			for (int i = 0; i < 2; i++)
			{
				result[i] = new List<InOutRelPair>();
				lbl_dict[i] = new Dictionary<string, InOutRelPair>();
			}
			int rel_idx = 0;
			foreach (var rel in rels)
			{
				int tgix = rel.trm.tgr.tgix;
				InOutRelPair iorp;
				if (rel.s_tag.Length > 0 && lbl_dict[tgix].ContainsKey(rel.s_tag))
					iorp = lbl_dict[tgix][rel.s_tag];
				else
				{
					iorp = new InOutRelPair();
					result[tgix].Add(iorp);
					if (rel.s_tag.Length > 0)
						lbl_dict[tgix][rel.s_tag] = iorp;
				}

				if (rel.trm.IsInRel)
					iorp.in_rel = rel_idx++;
				else
					iorp.out_rel = rel_idx++;
			}
			return result;
		}
#endif

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		TransferRule(IRtParent rts, TransferGrammar tg, String name, TfsBuilder.Xdl xdlb, SlotDictionary<String, TransferMap> maps)
			: base(rts, tg, xdlb)
		{
			this.Name = name;
			this.maps = maps;

#if TRANSFER_GRAMMAR_FLAGS
			req_flags = new IList<int>[Count];
			set_flags = new IList<int>[Count];
			ex_flags = new IList<int>[Count];

			for (int i = 0; i < Count; i++)
			{
				req_flags[i] = this[i].reqFlags;
				set_flags[i] = this[i].setFlags;
				ex_flags[i] = this[i].exFlags;
			}
#endif
			Debug.Print("tags: {0}", Roots.Select(ts => ts.tfs.GetIx1RmGgix(ts.ix1).Tag).StringJoin(" "));
		}

		public String Name { get; set; }

		public Tfs Expanded { get { return this; } }

		public Tfs Definition
		{
			get { throw not.impl; }
		}

		SlotDictionary<String, TransferMap> maps;

#if TRANSFER_GRAMMAR_FLAGS
		public IList<int>[] req_flags;
		public IList<int>[] set_flags;
		public IList<int>[] ex_flags;
#endif

		///////////////////////////////////////////////////////////////////////
		/// ITransferRule
		_rule_membership_helper rgh;
		public bool IsUsedInGroups { get { return rgh.IsUsedInGroups; } }
		public IList<TransferRuleGroup> GroupMembership { get { return rgh.GroupMembership; } }
		public void AddRuleGroup(TransferRuleGroup trg) { rgh.AddRuleGroup(trg); }
		public void ApplyRule(MultiRootTfs inp_rels, MultiRootTfs hyp_rels)
		{
			throw not.impl;
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		/// IRenderNode:
		public String Label { get { return Name; } }
		public string CorefTag { get { return null; } }
		public IEnumerable<IRenderPairs> FeatValuePairs
		{
			get
			{
				return Roots
					.GroupBy(ts1 => _xts_ext.trm(ts1, tg), ts2 =>
					{
						IRenderNode rn = ts2.tfs.RenderNode;
						int rel_tag = _xts_ext.tagged_rel_mode(ts2).Tag;
						if (rel_tag != 0)
							return new LabeledFeatValueNode(String.Format("{0}:", (Char)(rel_tag + 'a' - 1)), rn);
						return rn;
					})
					.OrderBy(g => g.Key.tgr.tgix)
					.Select(g => new FeatureValueNode(g.Key.ToString(), new ListValueNode(g), true));
			}
		}
		/// 
		///////////////////////////////////////////////////////////////////////



		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public class XdlRel : IComparable<XdlRel>
		{
			public XdlRel(TransferRelMode trm, String s_tag, String s_rel, String s_extra)
			{
				var tg = (this.trm = trm).tg;	/// tag number will be filled in to tgrm later
				this.s_tag = s_tag.Trim();
				s_rel = s_rel.Trim();
				if (s_rel[0] != '[' || s_rel[s_rel.Length - 1] != ']')
					throw new TransferGrammarLoadException("expecting [ ... ]");

				this.tdl = new agree.Tdl(tg, s_rel + ".") { Filename = tg.filename };
				this.s_extra = s_extra.Trim();

#if TRANSFER_GRAMMAR_FLAGS
				this.setFlags = new List<int>();
				this.reqFlags = new List<int>();
				this.exFlags = new List<int>();
				ExtractFlags(trm.tg, s_extra);
#endif
			}

			public TransferRelMode trm;
			public readonly String s_tag;
			public readonly agree.Tdl tdl;
			public readonly String s_extra;

#if TRANSFER_GRAMMAR_FLAGS
			public List<int> setFlags;
			public List<int> reqFlags;
			public List<int> exFlags;

			public void ExtractFlags(TransferGrammar tg, String s_flags)
			{
				//this is not fully functional at the moment. Only "set-flags" or "req-flags" can be specified.
				//Need to alter notation in order to support multiple flag types on an EP.
				string[] flagTypeSplit = s_flags.Split(':');
				if (flagTypeSplit[0].Trim() == "set-flags")
				{
					string[] setFlagsSplit = flagTypeSplit[1].Trim().Split(',');
					foreach (string s in setFlagsSplit)
					{
						int idx;
						if (!tg.flag_name_map.TryGetValue(s, out idx))
							idx = tg.NextFlagIdx;

						setFlags.Add(idx);
						exFlags.Add(idx);
					}
				}
				else if (flagTypeSplit[0].Trim() == "req-flags")
				{
					string[] setFlagsSplit = flagTypeSplit[1].Trim().Split(',');
					foreach (string s in setFlagsSplit)
					{
						int idx;
						if (!tg.flag_name_map.TryGetValue(s, out idx))
							idx = tg.NextFlagIdx;

						reqFlags.Add(idx);
					}
				}
			}
#endif

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// 
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static IEnumerable<XdlRel> _get_reldefs(TransferRelMode trm, String s_xdl)
			{
				StringBuilder sb_cont = new StringBuilder();
				String s_tag = "";
				int i = 0;
				int bracket_nest = 0;
				while (i < s_xdl.Length)
				{
					Char ch = s_xdl[i];
					if (ch == '[')
					{
						bracket_nest++;
					}
					else if (ch == ']')
					{
						if (bracket_nest == 0)
							throw new Exception();

						if (--bracket_nest == 0)
						{
							sb_cont.Append(ch);

							StringBuilder sb_outer = new StringBuilder();
							while (++i < s_xdl.Length && (ch = s_xdl[i]) != '[')
								sb_outer.Append(ch);

							yield return new XdlRel(trm, s_tag, sb_cont.ToString(), sb_outer.ToString());
							s_tag = "";
							sb_cont.Clear();
							sb_outer.Clear();
							continue;
						}
					}
					else if (bracket_nest == 0 && !Char.IsWhiteSpace(ch))
					{
						int i_tag_start = i;
						s_tag = null;
						while (i < s_xdl.Length)
						{
							ch = s_xdl[i++];
							if (ch == ':' || Char.IsWhiteSpace(ch))
							{
								s_tag = s_xdl.Substring(i_tag_start, i - i_tag_start - 1);
								break;
							}
							else if (ch == '[')
								throw new TransferGrammarLoadException("relation label must be followed by ':'");
							else if (TdlTokenizer.ident_stop.Contains(ch))
								throw new TransferGrammarLoadException("invalid character in relation label");
						}
						if (s_tag == null)
							throw new TransferGrammarLoadException("label not defined");
						continue;
					}

					sb_cont.Append(ch);
					i++;
				}
				if (bracket_nest != 0)
					throw new TransferGrammarLoadException("missing ']'");
			}

			public int CompareTo(XdlRel other) { return trm.CompareTo(other.trm); }

			public override String ToString()
			{
				String s = s_tag;
				if (s != "")
					s += ": ";
				s += (String)tdl.Content;
				if (!String.IsNullOrWhiteSpace(s_extra))
					s += " (" + s_extra + ")";
				return s;
			}
		};
	};
}